/*
 * rm.c
 * Jonathan Marokhovsky
 * WPI 2011
 * Possible arguments:
 *	-f
 *	-h
 *	-r
 *
 */

#include <stdio.h>
#include <stdlib.h> // for getenv()
#include <sys/stat.h> // for stat()
#include <sys/types.h>
#include <unistd.h>
#include <dirent.h>
#include <string.h>

void usage(void);
int dirOp(char *dir, int forced);
char *getenv(const char *name);
void newStat(const char *file, struct stat *fileBuffer);
int moveToTrash(char *file);
int trashDir(char *dir, struct stat fileBuffer);
int forceDelete(char *file);
int forceDirDelete(char *dir, struct stat fileBuffer);
void isDir(struct stat fileBuffer);
int stringLength(char *string);

int globalDebug = 0;
char *trash;

int main(int argc, char *argv[])
{
	int c, i, inOptList, indexToAdd;
	int fFlag = 0, rFlag = 0, errflag = 0, ret = 0;
	int optCount = 0; // opt count is the amount of options supplied
	int lessArgC = argc - 1; // the first argument is always the function call.
	int fileCount = 0; // the amount of files to delete
	char *filesToDel[lessArgC]; // this is to work with wildcards
	int optionListLoc[lessArgC]; // a list of all the options found.
	char *file = NULL;
	char *home;
	char theHome[257];
	char theTrash[257];
	extern int optind;
	extern char **environ;	/* externally declared */
	struct stat buf[lessArgC];
	/*DIR *dp; // might not need this here
	struct dirent *d;*/
	trash = theTrash;
	trash = getenv("TRASH"); // assume we have this.
	
	if (!trash) // if not, ABORT
	{
		fprintf(stderr, "ERROR: no trash environment variable set.\n");
		fprintf(stderr, "ERROR: set it with 'export TRASH=[trash directory]' in the shell.\n");
		usage();
	}


	for (ret = 0; ret < lessArgC; ret++)
	{
		filesToDel[ret] = NULL;
	}
	ret = 0;

	if (argc < 2)
	{
		errflag++;
	}
	else
	{
		while ((c = getopt(argc, argv, "hfrd")) != EOF)
		{
			indexToAdd = optind - 1;
			switch (c)
			{
				case 'f': // force flag
					if(globalDebug) printf("DEBUG: Adding %d to the list of opt locations because there is an -f.\n", indexToAdd);
					optionListLoc[optCount] = indexToAdd; // add the option's location to the list
					optCount++;
					fFlag++;
					break;
				case 'r': // recursive flag
					if(globalDebug) printf("DEBUG: Adding %d to the list of opt locations because there is an -r.\n", indexToAdd);
					optionListLoc[optCount] = indexToAdd;
					optCount++;
					rFlag++;
					break;
				case 'd': // debug mode
					globalDebug = 1;
					optionListLoc[optCount] = indexToAdd;
					optCount++;
					break;
				case '?':
				case 'h':
				default:
					if(globalDebug) printf("DEBUG: Adding %d to the list of opt locations because there is an -r.\n", indexToAdd);
					optionListLoc[optCount] = indexToAdd;
					optCount++;
					errflag++;
			}
		}
	}

	if (errflag) usage();

	/* get the locations of all paths to remove */
	for (i = 1; i < argc; i++) // don't start at 0 because we already know argv[0] is the name of the operation.
	{
		inOptList = 0;
		for (c = 0; c < optCount; c++)
		{
			if (i == optionListLoc[c]) inOptList++;
		}
		if(!inOptList)
		{
			if(globalDebug) printf("DEBUG: This is not in the option list.\n");
			if(globalDebug) printf("\tDEBUG: filename: %s\n\tDEBUG: filecount: %d\n", argv[i], fileCount + 1);
			filesToDel[fileCount] = argv[i];
			file = filesToDel[fileCount];
			newStat(file, &buf[fileCount]);
			fileCount++;
		}
	}

	/* if only the f flag was put in, just delete the file if it's not a directory */
	if (rFlag)
	{
		if(globalDebug) printf("DEBUG: There was an rFlag found!\n");
		if(fFlag)
		{
			if(globalDebug) printf("DEBUG: There was also an fFlag found!\n");
			// force delete of a directory
			for (i = 0; i < fileCount; i++)
			{
				if(globalDebug) printf("DEBUG: Trying to remove '%s'.\n", filesToDel[i]);
				ret = forceDirDelete(filesToDel[i], buf[i]);
				if (ret)
				{
					perror("Failed:");
					exit(ret);
				}
			}
		}
		else
		{
			// Send the directory to the trash
			for (i = 0; i < fileCount; i++)
			{
				if(globalDebug) printf("DEBUG: Trying to move '%s' to the trash.\n", filesToDel[i]);
				ret = trashDir(filesToDel[i], buf[i]);
				if (ret)
				{
					perror("Failed:");
					exit(ret);
				}
			}
		}
	}
	else 
	{
		/* Make sure there are no directories in the array since the r option was not used */
		for (i = 0; i < fileCount; i++)
		{
			isDir(buf[i]);
		}
		if (fFlag)
		{
			if(globalDebug) printf("DEBUG: There was an fFlag found!\n");
			if(globalDebug) printf("DEBUG: The file count is %d.\n", fileCount);
			for (i = 0; i < fileCount; i++)
			{
				if(globalDebug) printf("DEBUG: Trying to remove '%s'.\n", filesToDel[i]);
				ret = forceDelete(filesToDel[i]);
				if (ret)
				{
					perror("Failed:");
					exit(ret);
				}
			}
		}
		else
		{
			if(globalDebug) printf("DEBUG: No flags were found!\n");
			if(globalDebug) printf("DEBUG: The file count is %d.\n", fileCount);
			for (i = 0; i < fileCount; i++)
			{
				if(globalDebug) printf("DEBUG: Trying to remove '%s'.\n", filesToDel[i]);
				ret = moveToTrash(filesToDel[i]);
				if (ret)
				{
					perror("Failed:");
					exit(ret);
				}
			}
		}
	}
	// do what needs to be done.
	return ret;
}

/* print a usage message and quit */
void usage(void)
{
	fprintf(stderr, "Usage: rm [OPTION]... FILE...\n");
	fprintf(stderr, "Send FILE to the trash.\n");
	fprintf(stderr, "\t-f\t\tforce a complete remove, skip the trash can\n");
	fprintf(stderr, "\t-h\t\tdisplay this help message\n");
	fprintf(stderr, "\t-r\t\trecurse directories\n");
	exit(1);
}

/* Move the given file to the trash
 * This returns 0 if there was no error
 * If there was an error this returns a number greater than 1
 */
int moveToTrash(char *file)
{
	int ret1, ret;
	char *newFile;
	int fileLen = /*stringLength(trash) + stringLength(file) + 1*/256;
	char theNewFile[fileLen];
	
	newFile = theNewFile;
	strncpy(newFile, trash, fileLen);
	strncat(newFile, "/", fileLen); // as a safeguard, add a / between
	strncat(newFile, file, fileLen);
	if(globalDebug) printf("DEBUG: The trash is at %s.\n", trash);
	if(globalDebug) printf("DEBUG: This will move %s to the trash.\n", file);
	if(globalDebug) printf("DEBUG: The new file will be at %s\n", newFile);
	// This first sends a link from the file to the trash.
	ret1 = link(file, newFile);
	// Now it's safe to unlink
	ret = forceDelete(file);
	if (ret1 < ret)
	{
		ret = ret1;
	}
	return ret;
}

/* trashDir() removes the given directory
 * If there is an error in the delete it returns -1,
 * if nothing went wrong it returns a 0.
 */
int trashDir(char *dir, struct stat fileBuffer)
{
	int ret;
	char *newDir;
	int nameLen = 256;
	char theNewDir[nameLen];
	
	newDir = theNewDir;
	strncpy(newDir, trash, nameLen);
	strncat(newDir, "/", nameLen);
	strncat(newDir, dir, nameLen);
	if (globalDebug) printf("DEBUG: The trash is at %s.\n", trash);
	if (globalDebug) printf("DEBUG: This will move %s to the trash.\n", dir);
	if (globalDebug) printf("DEBUG: The new file will be at %s\n", newDir);
	// send the file to the trash
	if (S_ISDIR(fileBuffer.st_mode))
	{	
		if (globalDebug) printf("DEBUG: %s is a directory\n", dir);
		// if the dir has things in it, call trashDir() otherwise
		mkdir(newDir, fileBuffer.st_mode);
		dirOp(dir, 0);
		ret = forceDirDelete(dir, fileBuffer);
	}
	else
	{
		if (globalDebug) printf("DEBUG: %s is a file, sending to trash\n", dir);
		ret = moveToTrash(dir);
	}
	return ret;
}

/* Force delete skips the trash and deletes the given file.
 * If there is an error in the delete it returns -1,
 * otherwise it returns 0.
 */
int forceDelete(char *file)
{
	int ret;
	ret = unlink(file);
	return ret;
}

int dirOp(char *dir, int forced)
{
	DIR *dp;
	struct dirent *entry;
	int fileSize = 257;
	int tempSize = fileSize * 2;
	int ret, ret1;
	char *file;
	char theFile[fileSize];
	char *temp;
	char theTemp[tempSize];
	struct stat buf;
	
	file = theFile;
	temp = theTemp;
	
	if(globalDebug) printf("DEBUG: '%s' is a directory\n", dir);
		dp = opendir(dir);
		if (dp == NULL)
		{
			perror("open");
			exit(1);
		}
		entry = readdir(dp);
		file = entry->d_name;
		if(globalDebug) printf("DEBUG: '%s' should be ./\n", file);
		entry = readdir(dp); // skip ahead two so it's not ./ or ../
		file = entry->d_name;
		if(globalDebug) printf("DEBUG: '%s' should be ../\n", file);
		entry = readdir(dp);
		if (entry)
		{
			file = entry->d_name;
			if(globalDebug) printf("DEBUG: '%s' is the first file looked at\n", file);
		}
		else
		{
			if(globalDebug) printf("DEBUG: '%s' is now empty\n", dir);
		}
		while (entry) // while the directory is non-empty, keep calling this func
		{
			file = entry->d_name;
			if(globalDebug) printf("DEBUG: '%s' has a child, '%s'\n", dir, file);
			strncpy(temp, dir, stringLength(temp));
			strncat(temp, "/", stringLength(temp));
			strncat(temp, file, stringLength(dir) + 1 + stringLength(file));
			if(globalDebug) printf("DEBUG: This is what will be passed to newStat: '%s'\n", temp);
			newStat(temp, &buf);
			if (forced)
			{
				ret1 = forceDirDelete(temp, buf);
			}
			else
			{
				ret1 = trashDir(temp, buf);
			}
			if(globalDebug) printf("DEBUG: Back in %s\n", dir);
			entry = readdir(dp);
			if (entry)
			{
				if(globalDebug) printf("DEBUG: found a new entry\n");
			}
			else
			{
				if(globalDebug) printf("DEBUG: no other entries found\n");
			}
		}
		closedir(dp);
		if (ret1 < ret)
		{
			ret = ret1;
		}
		ret = rmdir(dir);
		if(globalDebug) printf("DEBUG: just deleted '%s'\n", dir);
		return ret;
}

/* Force directory delete does a recursive delete instead of a normal delete on the given file.
 * If there is an error in the delete it returns -1.
 */
int forceDirDelete(char *dir, struct stat fileBuffer)
{
	int ret;
	/* if it's a directory rm the directory */
	if (S_ISDIR(fileBuffer.st_mode))
	{
		ret = dirOp(dir, 1);		
	}
	else // otherwise, it's a file, remove the file
	{
		if(globalDebug) printf("DEBUG: '%s' is a file\n", dir);
		ret = forceDelete(dir);
	}
	return ret;
}

/* Exits if the given file buffer states it is a directory, otherwise this does "nothing".*/
void isDir(struct stat fileBuffer)
{
	if (S_ISDIR(fileBuffer.st_mode))
	{
		fprintf(stderr, "Use the -r option to remove directories\n");
		exit(2);
	}
}

/* NewStat() does a check to make sure stat() completed correctly */
void newStat(const char *file, struct stat *fileBuffer)
{
	int ret;
	/* check the stats of the file and put them in buf */
	ret = stat(file, fileBuffer);
	if (ret != 0)
	{
		perror("Failed in newStat:");
		exit(ret);
	}
}

int stringLength(char *string)
{
	int ret;
	ret = sizeof(string)/sizeof(char);
	return ret;
}

